package com.insa.flopenshop;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.TreeSet;

/**
 * Classe permettant de stocker une solution à une instance du problème
 * OpenShopInstance
 *
 * @author strokyl
 *
 */
public class Solution {

	private final Instance instance;
	private final LinkedList<Placement>[] fileRessourceUniverselle;

	// because Java Suck
	@SuppressWarnings("unchecked")
	public Solution(Instance instance) {
		super();
		this.instance = instance;
		this.fileRessourceUniverselle = new LinkedList[instance
			.getNumRessouceUniversel()];
		for (int i = 0; i < fileRessourceUniverselle.length; i++) {
			fileRessourceUniverselle[i] = new LinkedList<Placement>();
		}
	}

	public Instance getInstance() {
		return instance;
	}

	public LinkedList<Placement>[] getFileRessourceUniverselle() {
		return fileRessourceUniverselle;
	}

	/**
	 *
	 * @return les dernier placements sur toute les files dans l'ordre de
	 * fin
	 */
	public PriorityQueue<Placement> getPlacementsParOrdreDeFin() {

		PriorityQueue<Placement> result = new PriorityQueue<Placement>(instance.getNumSousTache(), new Comparator<Placement>() {

			@Override
			public int compare(Placement o1, Placement o2) {
				return o1.getDateFin() - o2.getDateFin();
			}

		});

		Placement last;
		for (int i = 0; i < fileRessourceUniverselle.length; i++) {
			if (fileRessourceUniverselle[i].isEmpty()) {
				result.add(new Placement(0, instance, new SousTacheVide(instance), i));
			} else {
				last = fileRessourceUniverselle[i].getLast();
				result.add(last);
			}
		}

		return result;
	}

	/**
	 * Ajouter un placement dans une file
	 *
	 * @param placement placement à ajouter
	 */
	public void ajouterPlacement(Placement placement) {
		if(!fileRessourceUniverselle[placement.getRessourceUniverselle()].isEmpty()){
			int fin = fileRessourceUniverselle[placement.getRessourceUniverselle()].getLast().getDateFin();
			int deb = placement.getDateDepart();
			if(fin > deb)
				System.out.println("Warning : Ajout d'un placement invalide !");
		}
		fileRessourceUniverselle[placement.getRessourceUniverselle()].addLast(placement);
		
	}

	/**
	 * Retourne un set de tache qui peut être placé après une certaine date
	 * sans violer des contraintes par rapport au sous tache qui ne seront
	 * pas terminé à ce moment la
	 *
	 * @param date
	 * @return liste ordonnee par taille de sous tache croissante
	 */
	public TreeSet<SousTache> getTachePlacable(int date) {
		TreeSet<SousTache> result = instance.getAllSousTache();
		TreeSet<SousTache> compatiblePlacement;
		Iterator<Placement> iter;
		Placement placement;

		for (LinkedList<Placement> file : fileRessourceUniverselle) {
			iter = file.descendingIterator();
			while (iter.hasNext()) {
				placement = iter.next();
				if (placement.getDateFin() > date) {
					compatiblePlacement = instance
						.getCompatibleSousTache(placement.getSousTache());
					result.retainAll(compatiblePlacement);
				} else {
					break;
				}
			}
		}

		return result;
	}
	
	/**
	 * Retourne un set de tache qui peut être placé après une certaine date
	 * sans violer des contraintes par rapport au sous tache qui ne seront
	 * pas terminé à ce moment la, suivant l'ordre de la liste
	 *
	 * @param date, ordre
	 * @return liste ordonnee selon l'ordre passe en parametre
	 */
	public ArrayList<SousTache> getTachePlacableOrdreListe(int date, ArrayList<SousTache> ordre) {
		ArrayList<SousTache> result = new ArrayList<SousTache>(ordre);
		TreeSet<SousTache> compatiblePlacement;
		Iterator<Placement> iter;
		Placement placement;
		
		//Dans chaque RU
		for (LinkedList<Placement> file : fileRessourceUniverselle) {
			iter = file.descendingIterator();
			//Parcours les placements ne partant de la fin
			while (iter.hasNext()) {
				placement = iter.next();
				//Si un placement n'est pas termine a date, on retire de la liste toutes les sous taches non compatibles avec ce placement
				if (placement.getDateFin() > date) {
					compatiblePlacement = instance.getCompatibleSousTache(placement.getSousTache());
					result.retainAll(compatiblePlacement);
				} else {
					break;
				}
			}
		}

		return result;
	}

	/**
	 * Retourne un set de tache qui peut être placé après une certaine date
	 * sans violer des contraintes par rapport au sous tache qui ne seront
	 * pas terminé à ce moment la, dans un ordre aleatoire
	 *
	 * @param date
	 * @return liste ordonnee aleatoirement
	 */
	public Iterable<SousTache> getTachePlacableAleatoire(int date) {
		ArrayList<SousTache> result = new ArrayList<SousTache>(instance.getAllSousTache());
		Collections.shuffle(result);
		TreeSet<SousTache> compatiblePlacement;
		Iterator<Placement> iter;
		Placement placement;

		for (LinkedList<Placement> file : fileRessourceUniverselle) {
			iter = file.descendingIterator();
			while (iter.hasNext()) {
				placement = iter.next();
				if (placement.getDateFin() > date) {
					compatiblePlacement = instance.getCompatibleSousTache(placement.getSousTache());
					result.retainAll(compatiblePlacement);
				} else {
					break;
				}
			}
		}

		return result;
	}

	/**
	 * Retourne vrai si et seulement si on peut placer la tache sousTache
	 * à une date donné sans conflit
	 * @param date
	 * @param sousTache
	 * @return 
	 */
	public boolean estPlacable(int date, SousTache sousTache) {
		Iterator<Placement> iter;
		Placement placement;
		for (LinkedList<Placement> file : fileRessourceUniverselle) {
			iter = file.descendingIterator();
			while (iter.hasNext()) {
				placement = iter.next();
				if (placement.getDateFin() > date) {
					if(placement.getSousTache().enConflitAvec(sousTache)) {
						return false;
					}
				} else {
					break;
				}
			}
		}

		return true;
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("Solution : \n");
		for (LinkedList<Placement> lp : fileRessourceUniverselle) {
			builder.append(lp);
			builder.append("\n");
		}

		return builder.toString();
	}
	
	/**
	 * Retourne le temps total d'execution des taches
	 *
	 * @return Temps maximal sur l'ensemble des RU
	 */
	public int getTotalTime() {
		int result = Integer.MIN_VALUE;

		for (LinkedList<Placement> pl : fileRessourceUniverselle) {
			if (!pl.isEmpty()) {
				result = Math.max(pl.getLast().getDateFin(), result);
			}
		}

		return result;
	}
	
	/**
	 * Retourne la taille de la plus petite RU
	 *
	 * @return Taille minimale sur l'ensemble des RU
	 */
	public int getMinSize() {
		int result = fileRessourceUniverselle[0].size();

		for (LinkedList<Placement> pl : fileRessourceUniverselle) {
			if (!pl.isEmpty()) {
				int size = pl.size();
				if(size < result)
					result = size;
			}
		}

		return result;
	}
	
	/**
	 * Rend l'instance solution valide en decalant les taches vers la droite pour eviter les collisions
	 *
	 */
	public void corriger(){
		//Recupere l'ordre des sous taches dans chaque RU
		ArrayList<ArrayList<SousTache>> ordreSolution = new ArrayList<ArrayList<SousTache>>();
		for(int i=0 ; i < fileRessourceUniverselle.length ; i++){
			LinkedList<Placement> pl = fileRessourceUniverselle[i];
			if (!pl.isEmpty()) {
				ordreSolution.add(new ArrayList<SousTache>());
				for(Placement p : pl){
					ordreSolution.get(i).add(p.getSousTache());
				}
			}
		}
		
		//Replace les taches dans les RU en ajoutant des vides pour eviter les collisions
		int currentIndex;
		boolean fini = false;
		int numRU = instance.getNumRessouceUniversel();
		int[] tempsFin = new int[numRU];
		LinkedList<Placement>[] listeRU = getFileRessourceUniverselle();
		for(int i = 0 ; i<numRU ; i++){
			tempsFin[i]=0;
			listeRU[i]=new LinkedList<Placement>();
		}
		
		// Parcours les sous taches dans l'ordre de leur position dans la RU et les place de maniere a respecter les contraintes
		currentIndex = 0;
		while(!fini){
			fini = true;
			
			for(int i=0 ; i < numRU ; i++){
				if(currentIndex < ordreSolution.get(i).size()){
					fini = false;
					SousTache sousTacheAPlacer = ordreSolution.get(i).get(currentIndex);
					
					//Pour chacune des autres RU on teste s'il n'y a pas de collision, et s'il y en a on deplace la tache vers la droite
					for(int j = 0 ; j < numRU ; j++){
						//Si la derniere tache placee sur RUj se termine apres le temps ou on souhaite placer la sous tache courante
						if(j != i && tempsFin[j]>tempsFin[i]){
							//On parcourt RUj pour tester les collisions avec la sous tache courante
							LinkedList<Placement> currentRU = listeRU[j];
							for(int k = 0 ; k < currentRU.size() ; k++){
								Placement currentPlacement = currentRU.get(k);
								//Si intersection
								if(currentPlacement.getDateDepart() < tempsFin[i]+sousTacheAPlacer.getTime() && currentPlacement.getDateFin() > tempsFin[i]){
									//Si une sous tache qui s'execute en parallele sur une autre ressource universelle 
									//utilise la meme ressource dediee que la sous tache courante ou appartient a la meme tache que la sous tache courante
									if(currentPlacement.getRessourceDedie() == sousTacheAPlacer.getRessourceDedie() || currentPlacement.getTache() == sousTacheAPlacer.getTache()){
										//On decale le temps de debut de la sous tache courante
										tempsFin[i] = currentPlacement.getDateFin();
									}
								}
							}
						}
					}
					
					//On place la sous tache courante
					ajouterPlacement(new Placement(tempsFin[i], instance, sousTacheAPlacer, i));
					
					//On met a jour le temps de fin
					tempsFin[i] = tempsFin[i]+sousTacheAPlacer.getTime();
					
				}
			}
			currentIndex++;
		}
	}				

	/**
	 * Retourne un matrice qui permet de savoir en temps constant si une sous tache 
	 * est déjà placé sur le problème
	 * 
	 * @return matrice contenant true si la tache n'est pas encore placee, false sinon
	 */
	public boolean[][] getTabTachePlace() {
		boolean result[][] = new boolean[instance.getNumTache()][instance.getNumRessourceDedie()];
		SousTache sousTache;
		for (LinkedList<Placement> file : fileRessourceUniverselle) {
			for(Placement placement : file) {
				sousTache = placement.getSousTache();
				result[sousTache.getTache()][sousTache.getRessourceDedie()] = true;
			}
		}

		return result;
	}

	/**
	 * Retourne le nombre de sous taches actuellement placees dans l'ensemble des RU
	 * 
	 * @return nombre de sous taches placees
	 */
	int getNombreSousTachePlace() {
		int result = 0;

		SousTache sousTache;
		for (LinkedList<Placement> file : fileRessourceUniverselle) {
			for(Placement placement : file) {
				result++;
			}
		}

		return result;
	}

	/**
	 * Retourne l'ensemble des sous taches placees dans l'ordre croissant de leur date de placement
	 * 
	 * @return liste des sous taches ordonnee par date de depart
	 */
	public ArrayList<SousTache> getOrdreSousTaches() {
		ArrayList<SousTache> ordre = new ArrayList<SousTache>();
		int numRU = instance.getNumRessouceUniversel();
		int selectedIndex;
		Placement selectedPlacement;
		int[] indexes = new int[numRU];

		for(int i = 0 ; i<numRU ; i++){
			indexes[i]=0;
		}
		
		// Parcours chronologiquement les placements et ajoute la sous tache correspondante a la liste
		while(true){
			// Selectionne le premier index qui n'a pas atteint la fin de la file
			LinkedList<Placement>[] fileRU = getFileRessourceUniverselle();
			selectedIndex = 0;
			while(
					(selectedIndex < numRU) && (
							(fileRU[selectedIndex].size() == 0) || (
									(fileRU[selectedIndex].size() > 0) && (indexes[selectedIndex] == fileRU[selectedIndex].size())
							)
					)
				){
					selectedIndex++;
			}
			
			// Fini si tous les index ont atteint la fin de leur liste (ou que la liste etait vide)
			if(selectedIndex == numRU)
				break;
			
			// Recupere l'index de la file avec la date de d�part la plus faible et le placement correspondant
			for(int i = 0 ; i<numRU ; i++){
				if(indexes[i] < fileRU[i].size()){
					if(fileRU[i].get(indexes[i]).getDateDepart() < fileRU[selectedIndex].get(indexes[selectedIndex]).getDateDepart())
						selectedIndex = i;
				}
			}
			selectedPlacement = fileRU[selectedIndex].get(indexes[selectedIndex]);
			
			//Ajoute la sous tache correspondante a la liste
			ordre.add(selectedPlacement.getSousTache());
			
			// On incremente l'index de la file qui vient d'etre traitee
			indexes[selectedIndex]++;
		}

		return ordre;
	}
}
